#pragma once
#include "lib.h"


namespace han {

	

	template <typename> class shared_ptr;
	template <typename T> 
	void swap(han::shared_ptr<T> &lhs, han::shared_ptr<T> &rhs) {
		using std::swap;
		swap(lhs.p, rhs.p);
		swap(lhs.ref_count, rhs.ref_count);
		swap(lhs.deleter, rhs.deleter);
	}
	template <typename T>
	han::shared_ptr<T> make_shared(T var) {
		han::shared_ptr<T> tmp(new T(var));
		return tmp;
	}
	class MyDelete
	{
	public:
		MyDelete(std::ostream& os = std::cerr) : os(os) {}
		template <typename It>
		void operator()(It *p) {
			os << "MY DELETE BY HAN" << endl;
			delete p;
		}
	private:
		std::ostream &os;
	};


	template <typename T>
	class shared_ptr
	{
		friend void swap(han::shared_ptr<T> &lhs,
			han::shared_ptr<T> &rhs);
	public:
		shared_ptr()
			: p(nullptr), ref_count(new std::size_t(1)), deleter(han::MyDelete{}) {}
		explicit shared_ptr(T* raw_ptr, std::function<void(T*)> d = han::MyDelete{})
			: p(raw_ptr), ref_count(new std::size_t(1)), deleter(d) {}
		shared_ptr(shared_ptr const& other)
			: p(other.p), ref_count(other.ref_count), deleter(other.deleter) {
			++*ref_count;
		}
		shared_ptr(shared_ptr &&other) noexcept
			: p(other.p), ref_count(other.ref_count), deleter(other.deleter) {
			other.p = nullptr;
			other.ref_count = nullptr;
		}
		shared_ptr& operator=(shared_ptr const& rhs) {
			++*rhs.ref_count;
			decrement_and_destroy();
			p = rhs.p;
			ref_count = rhs.ref_count;
			deleter = rhs.deleter;
			return *this;
		}
		shared_ptr& operator=(shared_ptr &&rhs) {
			::swap(*this, rhs);
			rhs.decrement_and_destroy();
			return *this;
		}
		//Conversion operator
		operator bool() const {
			return p ? true : false;
		}

		T& operator*() const {
			return *p;
		}
		T* operator->() const {
			return &*p;
		}

		auto use_count() const {
			return *ref_count;
		}
		auto get() const {
			return p;
		}
		auto unique() const {
			return 1 == *ref_count;
		}
		auto swap(shared_ptr &rhs) {
			::swap(*this, rhs);
		}
		auto reset() {
			decrement_and_destroy();
		}
		auto reset(T *ptr) {
			if (p != ptr) {
				decrement_and_destroy();
				p = ptr;
				ref_count = new std::size_t(1);
			}
		}
		auto reset(T *ptr, const std::function<void(T*)> &d) {
			reset(ptr);
			deleter = d;
		}
		~shared_ptr() {
			decrement_and_destroy();
		}
	private:
		T *p;
		std::size_t* ref_count;
		std::function<void(T*)> deleter;

		auto decrement_and_destroy() {
			if (p && 0 == --*ref_count) {
				delete ref_count, deleter(p);
			} else if (!p) {
				delete ref_count;
			}
			ref_count = nullptr;
			p = nullptr;

		}
	};
}